Skip to content
GitHub

Go Goroutine

golang 支持并发和并行, 可以同时跑多个协程 golang 使用 go <function name>() 创建并执行协程 协程本质是一个函数

import (
    . "fmt"
    "time"
    "sync"
)

var wg sync.WaitGroup                            // 定义 wg 辅助协程计数和执行

func main() {
    Println(time.Now())                          // 执行前打印时间

    wg.Add(2)                                    // 标记需要执行两个协程
    go func() {                                  // 新建协程, 类似于将函数放后台执行, main 直接接执行下一个函数
        cook()
        wg.Done()                                // 标记协程执行完毕
    }

    go func() {
        wash()
        wg.Done()
    }

    wg.Wait()                                    // 等待所有的协程执行完成
    Println(time.Now())                          // 所有协程执行完后打印时间
}

func cook() {
    time.Sleep(time.Second * 3)                  // sleep 3s
    Println("cook by machine use 3s")
}

func wash() {
    time.Sleep(time.Second * 2)
    Println("wash close by machine use 2s")
}

> 2023-04-01 21:51:20.692681383 +0800 CST m=+0.000016384
> wash close by machine use 2s
> cook by machine use 3s
> 2023-04-01 21:51:23.693482859 +0800 CST m=+3.000817861

golang 通过新建 2 个协程同时跑 main cook wash 函数, 3 个函数并行执行共花费 3s. main 也是一个协程, main 中若不设置 wg.Wait() 等待其余协程完成, main 执行完成后会关闭所有协程

为了确保协程同步, 或协程之间的数据传递, 通道是队列(先进先出)

通道容量为 0, 不能存值, 发送语句和结束语句需要都执行, 否则一方会一直等待另一方导致阻塞
发送语句先执行, 则发送语句阻塞, 等待接收语完后继续执行
接受语句先执行, 则接受语句阻塞, 等待发送语完后继续执行
以上特性常用于协程同步, 无缓冲通道也被称为同步通道

import (
    . "fmt"
    "time"
    "sync"
)

var wg sync.WaitGroup

func main() {
    Printf("start at %v\n", time.Now())
    ch := make(chan int, 0)                      // 初始化通道, 设置缓冲容量为 0
    wg.Add(1)
    go receive(ch)                               // 创建协程
    ch <- 3                                      // 向通道写入数据, 等待接收语句执行, 数据接收后执行下一句
    Printf("send at %v\n", time.Now())
}

func receive(c chan int) {
    time.Sleep(time.Second * 2)
    rec := <- c                                  // 接收通道数据, 若先执行则等待发送语句执行
    Printf("receive %d at %v\n", rec, time.Now())// 发送和接收执行同步
    wg.Done()
}

> start at 2023-04-01 22:52:35.842840438 +0800 CST m=+0.000018823
> receive 3 at 2023-04-01 22:52:37.843993741 +0800 CST m=+2.001172157
> send at 2023-04-01 22:52:37.844102485 +0800 CST m=+2.001280900

通道容量大于 0, 通道允许在容量未满时存值, 容量存满时变成无缓冲通道

import (
    . "fmt"
    "time"
    "sync"
)

var wg sync.WaitGroup

func main() {
    Printf("start at %v\n", time.Now())
    ch := make(chan int, 2)                      // 初始化通道, 设置缓冲容量为 2
    wg.Add(2)
    go receive(ch)                               // 创建协程接收通道的值
    go send(ch)                                  // 创建协程向通道传值
    wg.Wait()
    Printf("over at %v\n", time.Now())
}

func send(ch chan int) {
    ch <- 1
    ch <- 2
    close(ch)                                    // 向通道传值结束后关闭通道
    Printf("send 1,2 at %v\n", time.Now())
    wg.Done()
}

func receive(ch chan int) {
    time.Sleep(time.Second * 1)
    rec := []int{}
    for i := range ch {                          // 通道关闭后, 使用 range 读取通道值
        rec = append(rec, i)
    }
    Printf("receive %v at %v\n", rec, time.Now())// 发送和接收执行同步
    wg.Done()
}

> start at 2023-04-02 12:20:34.69317803 +0800 CST m=+0.000016435
> send 1,2 at 2023-04-02 12:20:34.693231007 +0800 CST m=+0.000069413
> receive [1 2] at 2023-04-02 12:20:35.693311209 +0800 CST m=+1.000149614
> over at 2023-04-02 12:20:35.693371227 +0800 CST m=+1.000209634

Go 中一些操作不是并发安全的, 需要锁保证同一时间一个资源只能被一个协程操作

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var str string

    add := func() {
        for i := 0; i < 500; i++ {
            str += "a"
        }
        wg.Done()
    }

    wg.Add(2)
    go add()
    go add()

    wg.Wait()
    fmt.Printf("str: %d\n", len(str))
}

$ go run .\main.go
> str: 827

两个协程同时操作 str, 存在数据竞争, 结果互相覆盖和预期不符

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var mu sync.Mutex
    var str string

    add := func() {
        for i := 0; i < 500; i++ {
            mu.Lock()
            str += "a"
            mu.Unlock()
        }
        wg.Done()
    }

    wg.Add(2)
    go add()
    go add()

    wg.Wait()
    fmt.Printf("str: %d\n", len(str))
}

$ go run .\main.go
> str: 1000

使用互斥锁, 保证同一时间资源只被一个协程操作